﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Common;

/// <summary>
/// 对于queryable的扩展
/// </summary>
public static class QueryableOrderByExtension
{
    /// <summary>
    /// 动态排序
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="query"></param>
    /// <param name="propertyName">需排序字段最后一位是+为正序 - 为倒叙</param>
    /// <returns></returns>
    public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> query, string propertyName)
    {
        ArgumentNullException.ThrowIfNull(query);
        var sortArray = CheckPropertyName(propertyName);
        // 通过反射生成泛型的方法  要指定泛型的类型
        return OrderBySortArray(query, sortArray);


    }
    /// <summary>
    /// 排序操作
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TProp"></typeparam>
    /// <param name="query"></param>
    /// <param name="sortArray"></param>
    /// <returns></returns>
    private static IQueryable<T> OrderBySortArray<T>(IQueryable<T> query, string[] sortArray)
    {
        var  tempQyery = query;
        //通过反射获得方法info   BindingFlags.NonPublic|BindingFlags.Static  获取私有的  静态方法
        var methodInfo = typeof(QueryableOrderByExtension).GetMethod("CreateOrderedQueryable",  BindingFlags.NonPublic|BindingFlags.Static);
        for (int i = 0; i < sortArray.Length; i++)
        {
            //解析 参数 是升序还是降序
            var item = sortArray[i];
            var isAsc = item.Last() != '-';
            var sortPropertyName = item.Substring(0, item.Length - 1);
            // 获取参数的info  IgnoreCase忽略大小写
            var propertyInfo = typeof(T).GetProperty(sortPropertyName,
                BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);// ??throw new ArgumentNullException(nameof(sortPropertyName));
            //通过反射创建泛型方法
            var method = methodInfo!.MakeGenericMethod(typeof(T), propertyInfo.PropertyType);
            // 对于字段进行排序操作
            tempQyery = (IOrderedQueryable<T>)method.Invoke(null, new object[] {tempQyery,propertyInfo,isAsc,i });
        }

        return tempQyery;
    }
    /// <summary>
    /// 通过表达式树 创建排序
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TProp"></typeparam>
    /// <param name="query"></param>
    /// <param name="info"></param>
    /// <param name="isAsc"></param>
    /// <param name="num"></param>
    /// <returns></returns>
    private static IOrderedQueryable<T> CreateOrderedQueryable<T, TProp>(IQueryable<T> query,PropertyInfo info,bool isAsc,int num)
    {
        var lambda = GetLamba<T, TProp>(info);
        if (num==0)
        {
            if (isAsc)
            {
                return query.OrderBy(lambda);
            }
            else
            {
                return query.OrderByDescending(lambda);
            }
        }
        else 
        {
            var tempQuery = query as IOrderedQueryable<T>;
            if (isAsc)
            {
                return tempQuery.ThenBy(lambda);
            }
            else
            {
                return tempQuery.ThenByDescending(lambda);
            }
        }


    }
    /// <summary>
    /// 校验输入的字符串
    /// </summary>
    /// <param name="propertyName"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    private static string[] CheckPropertyName(string propertyName)
    {
        ArgumentException.ThrowIfNullOrEmpty(propertyName);
        var sortArray = propertyName.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
        //var lastChars= sortArray.Select(p => p.Last()).ToList();
        if (!sortArray.All(p => p.Length >= 2 && (p.Last() == '-' || p.Last() == '+')))
        {
            throw new Exception("Illegal sort propertyName");
        }
        return sortArray;
    }
    /// <summary>
    /// 构建lambda
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TProp"></typeparam>
    /// <param name="propertyName"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    private static Expression<Func<T, TProp>> GetLamba<T, TProp>(PropertyInfo propertyInfo)
    {
        ArgumentNullException.ThrowIfNull(propertyInfo);
        if (propertyInfo.PropertyType != typeof(TProp)) throw new Exception(nameof(propertyInfo));
        var parameter = Expression.Parameter(typeof(T), "p");
        var lambda = Expression.Lambda<Func<T, TProp>>(Expression.Property(parameter, propertyInfo), parameter);
        return lambda;
    }
}



